Các khái niệm Hợp_ngữ

Trình hợp dịch (Assembler)

Thông thường, một trình hợp dịch hiện đại tạo ra mã đối tượng (object code) bằng cách phiên dịch các lệnh hợp ngữ thành mã thực thi (opcodes) và phân tích các biểu danh (symbolic names) ứng với các vùng nhớ cùng các thực thể khác.[10] Việc dùng các biểu danh để tham chiếu là một tính năng then chốt của các trình hợp dịch, nó tiết kiệm một khối lượng lớn công việc tính toán và sửa đổi thủ công sau mỗi lần cải tiến ứng dụng. Hầu hết các trình hợp dịch đều hỗ trợ macro nhằm giúp cho việc thay thế một nhóm lệnh bằng một định danh ngắn gọn. Trong quá trình dịch, nhóm lệnh tương ứng sẽ được chèn trực tiếp vào vị trí macro thay vì một lời gọi hàm (subroutine).

Một số trình hợp dịch cũng có thể thực hiện một số loại tối ưu hóa cụ thể theo tập lệnh. Một ví dụ cụ thể về điều này có thể là trình hợp dịch x86 phổ biến từ các nhà cung cấp khác nhau. Hầu hết trong số chúng có thể thực hiện thay thế lệnh nhảy (nhảy dài thay thế bằng nhảy ngắn hoặc tương đối) trong bất kỳ số lần vượt qua, theo yêu cầu. Những trình hợp dịch khác thậm chí có thể thực hiện sắp xếp lại đơn giản hoặc chèn các lệnh, chẳng hạn như một số trình hợp dịch cho kiến trúc RISC có thể giúp tối ưu hóa lịch trình tập lệnh hợp lý để khai thác kênh chuyền dữ liệu (pipeline) của CPU một cách hiệu quả nhất có thể.

Giống như các ngôn ngữ lập trình ban đầu như Fortran, Algol, CobolLisp, các trình hợp dịch đã có sẵn từ những năm 1950 và các thế hệ giao diện máy tính dựa trên văn bản đầu tiên. Tuy nhiên, các trình hợp dịch xuất hiện đầu tiên vì chúng đơn giản hơn nhiều so với trình biên dịch cho các ngôn ngữ bậc cao. Điều này là do mỗi mnemonic cùng với các chế độ địa chỉ và toán hạng của một lệnh dịch trực tiếp thành các biểu diễn số của lệnh đó, mà không có nhiều bối cảnh hoặc phân tích. Cũng đã có một số lớp dịch giả và trình tạo mã bán tự động có các thuộc tính tương tự cả hợp ngữ và ngôn ngữ bậc cao, với Speedcode có lẽ là một trong những ví dụ được biết đến nhiều hơn.

Có thể có một số trình biên dịch với cú pháp khác nhau cho một cấu trúc CPU hoặc tập lệnh cụ thể. Chẳng hạn, một lệnh để thêm dữ liệu bộ nhớ vào một thanh ghi trong bộ xử lý họ x86 có thể là add eax,[ebx], trong cú pháp gốc của Intel, trong khi điều này sẽ được viết là addl (%ebx),%eax trong cú pháp của AT&T được dùng trong GNU Assembler. Mặc dù xuất hiện khác nhau, các hình thức cú pháp khác nhau thường tạo ra cùng một mã máy. Xem bên dưới. Một trình biên dịch đơn cũng có thể có các chế độ khác nhau để hỗ trợ các biến thể trong các hình thức cú pháp cũng như các diễn giải ngữ nghĩa chính xác của chúng (như cú pháp FASM, cú pháp TASM, chế độ lý tưởng, v.v., trong trường hợp đặc biệt của lập trình hợp ngữ x86).

Các trình hợp dịch nói chung dễ tạo hơn so với các chương trình dịch cho ngôn ngữ cấp cao. Những trình hợp ngữ đầu tiên xuất hiện từ những thập niên 1950, trong buổi đầu sơ khai của máy tính đã tạo ra một bước ngoặt lớn đối với những lập trình viên vốn rất mệt mỏi vì việc lập trình bằng ngôn ngữ máy. Các trình hợp dịch hiện đại ngày nay, đặc biệt cho các dòng chip RISC như MIPS, Sun SPARCHP PA-RISC, thường tối ưu việc sắp xếp và đồng bộ các chỉ thị lệnh (instruction scheduling) để tận dụng các kênh chuyền dữ liệu (pipeline) của CPU một cách hiệu quả.

Số lần

Có hai loại trình hợp dịch dựa trên số lần truyền qua nguồn cần thiết (số lần trình biên dịch đọc nguồn) để tạo tệp đối tượng.

  • Trình hợp dịch một lần đi qua mã nguồn một lần. Bất kỳ ký hiệu nào được sử dụng trước khi được xác định sẽ yêu cầu "errata" ở cuối mã đối tượng (hoặc, ít nhất, không sớm hơn điểm mà biểu tượng được xác định) báo cho trình liên kết hoặc trình tải "quay lại" và ghi đè lên giữ chỗ đã được để lại nơi sử dụng biểu tượng chưa xác định.
  • Trình hợp dịch nhiều lần tạo một bảng có tất cả các ký hiệu và giá trị của chúng trong các lượt đầu tiên, sau đó sử dụng bảng trong các lần truyền sau để tạo mã.

Trong cả hai trường hợp, trình biên dịch phải có khả năng xác định kích thước của mỗi lệnh trên các đường chuyền ban đầu để tính địa chỉ của các ký hiệu tiếp theo. Điều này có nghĩa là nếu kích thước của một hoạt động đề cập đến một toán hạng được xác định sau phụ thuộc vào loại hoặc khoảng cách của toán hạng, trình biên dịch sẽ đưa ra ước tính bi quan khi lần đầu tiên gặp thao tác và nếu cần, hãy đệm nó bằng một hoặc nhiều lệnh "no-operation" trong một lần vượt qua hoặc errata. Trong một trình biên dịch với tối ưu hóa lỗ nhìn trộm, các địa chỉ có thể được tính toán lại giữa các lần chuyển để cho phép thay thế mã bi quan bằng mã được điều chỉnh theo khoảng cách chính xác từ mục tiêu.

Lý do ban đầu cho việc sử dụng bộ hợp dịch một lần là tốc độ hợp dịch - thường thì lần thứ hai sẽ yêu cầu tua lại và đọc lại nguồn chương trình trên băng hoặc đọc lại một chuỗi bìa đục lỗ. Các máy tính sau này có bộ nhớ lớn hơn nhiều (đặc biệt là lưu trữ đĩa), có không gian để thực hiện tất cả các xử lý cần thiết mà không cần đọc lại. Ưu điểm của trình hợp dịch nhiều lượt là việc không có errata làm cho quá trình liên kết (hoặc tải chương trình nếu trình biên dịch trực tiếp tạo mã thực thi) nhanh hơn.r.[11]

Ví dụ: trong đoạn mã sau, trình hợp dịch một lần có thể xác định địa chỉ của BKWD tham chiếu ngược khi hợp dịch câu lệnh S2, nhưng không thể xác định địa chỉ của FWD tham chiếu chuyển tiếp khi hợp dịch câu lệnh nhánh S1; thật vậy, FWD có thể không được xác định. Trình hợp dịch hai lần sẽ xác định cả hai địa chỉ trong lần 1, vì vậy chúng sẽ được biết khi tạo mã trong lần 2.

S1   B    FWD  ...FWD   EQU *  ...BKWD  EQU *  ...S2    B   BKWD

Trình hợp dịch bậc cao

Nhiều trình hợp dịch bậc cao còn hỗ trợ khả năng ngôn ngữ trừu tượng như:

Tham khảo phần Thiết kế ngôn ngữ bên dưới để rõ hơn.

Hợp ngữ (Assembly language)

Một chương trình viết bằng hợp ngữ bao gồm một chuỗi các lệnh (instructions) dễ nhớ tương ứng với một luồng các chỉ thị khả thi (executable) mà khi được dịch bằng một trình hợp dịch, chúng có khả năng nạp được vào bộ nhớ đồng thời thực thi được.Ví dụ, bộ vi xử lý x86/IA-32 có thể thực hiện được chỉ thị nhị phân sau (thể hiện ở dạng ngôn ngữ máy):

  • 10110000 01100001 (thập lục phân: 0xb061)

Lệnh trên tương đương với một chỉ thị hợp ngữ dễ nhớ hơn sau:

  • mov al, 061h

Chỉ thị lệnh trên có nghĩa là: gán giá trị thập lục phân 61 (97 dạng thập phân) cho thanh ghi trong bộ vi xử lý có tên là "al". Thuật từ "mov" là mã thực thi (operation code / opcode), được người thiết kế tập lệnh đặt tên thay thế cho từ "move", các đối/ tham số của lệnh theo sau và ngăn cách với opcode bởi một dấu phảy ",".

Trình hợp dịch thực hiện chuyển đổi hợp ngữ sang ngôn ngữ máytrình phân dịch (disassembler) thực hiện quá trình trên ngược lại. Không giống các ngôn ngữ bậc cao, các chỉ thị hợp ngữ cơ bản thường có mối liên hệ tương ứng 1-1 với các chỉ thị ngôn ngữ máy. Tuy nhiên trong một số trường hợp, một trình hợp dịch có thể bổ sung các lệnh giả (pseudo-instructions) vào tập lệnh ngôn ngữ máy nhằm cung cấp các chức năng được dùng thường xuyên. Hầu hết các trình hợp dịch đa chức năng đều cung cấp thêm một tập macro phong phú để nhà sản xuất thiết bị và lập trình viên có thể tạo các mã lệnh và các dãy dữ liệu phức tạp.

Mỗi kiến trúc máy tính đều có ngôn ngữ máy riêng và do đó cũng có hợp ngữ riêng, chúng phân biệt với nhau bằng số lượng và kiểu của các lệnh mà chúng hỗ trợ. Chúng cũng có thể khác nhau về số lượng và kích cỡ của các thanh ghi cũng như cách thể hiện các kiểu dữ liệu trong bộ lưu trữ (bộ nhớ). Hầu hết các máy tính công dụng chung đều có khả năng thực hiện cùng chức năng nhưng cách mà chúng thực hiện thì khác nhau, điều đó phản ánh sự khác nhau giữa các hợp ngữ tương ứng với mỗi kiểu máy tính.

Ngôn ngữ máy (Machine language)

Ngôn ngữ máy được xây dựng từ các chỉ thị và các lệnh rời rạc, tùy vào mỗi kiến trúc xử lý mà tập lệnh được xác lập bởi các đặc thù riêng:

Nhiều lệnh phức hợp được tạo dựng bằng cách kết hợp nhiều chỉ thị đơn giản với nhau, các chỉ thị này tuân theo nguyên lý máy tính Von Neumann, tức là thực thi tuần tự và rẽ nhánh theo lệnh phân luồng. Một số lệnh điển hình có mặt trong hầu hết các tập lệnh gồm có:

  • Lệnh gán
    • Gán cho một thanh ghi (một vùng nhớ tạm thời trong CPU) một giá trị hằng số xác định
    • Chuyển dữ liệu từ một vùng nhớ sang một thanh ghi hoặc ngược lại. Thao tác này dùng để chuẩn bị dữ liệu cho một tính toán sau đó hoặc để lưu kết quả của một tính toán trước đó.
    • Đọc /ghi dữ liệu từ/vào các thiết bị phần cứng
  • Lệnh cho tính toán
    • Cộng, trừ, nhân hoặc chia các giá trị chứa trong các thanh ghi và lưu kết quả vào một thanh ghi
    • Thực hiện các phép thao tác bit "và"/"hoặc" (AND/OR) trên một cặp thanh ghi, hoặc phép phủ định bit trên một thanh ghi
    • So sánh nhỏ hơn/lớn hơn/ bằng nhau giữa hai giá trị lưu trong hai thanh ghi
  • Lệnh điều khiển rẽ nhánh
    • Nhảy tới một vị trí trong chương trình và thực thi các lệnh ở đó
    • Nhảy tới một vị trí khác nếu một điều kiện nhất định được thỏa mãn
    • Nhảy tới một vị trí nhưng lưu lại vị trí của lệnh tiếp theo để làm điểm nhảy trở về (thường là một lời gọi hàm)

Một số máy tính bao gồm các chỉ thị lệnh phức hợp trong tập lệnh của chúng. Một lệnh phức hợp thường thực hiện những tác vụ cần nhiều chỉ thị lệnh trên nhiều máy khác nhau, chúng thực hiện trong nhiều bước, điều khiển nhiều đơn vị chức năng. Danh sách minh họa một số lệnh phức hợp:

  • Lưu lại nhiều thanh ghi trên ngăn xếp chỉ một lần
  • Di chuyển các khối vùng nhớ lớn
  • Các phép toán dấu phảy động phức tạp (sine, cosine, square root, etc.)
  • Các lệnh ALU liên kết với một toán hạng từ bộ nhớ thay vì với một thanh ghi

Một kiểu lệnh phức hợp được dùng phổ biến ngày nay là các phép toán SIMD hay các lệnh vector (vector instruction) có khả năng thực hiện cùng một phép toán số học trên nhiều phần của dữ liệu trong cùng một thời điểm. Các lệnh SIMD (single instruction multile data) cho phép thực hiện song song nhiều thuật toán liên quan đến xử lý âm thanh, hình ảnh và video một cách dễ dàng. Nhiều tập lệnh thực thi SIMD tích hợp trong CPU đã được thương mại hóa dưới các thương hiệu như MMXSSE, SSE2, SSE3, SSE4 (Intel), 3DNow! (AMD), AltiVec (IBM), tm3260tm5250 (Nexperia - Philips) …